package robot;

//imports
import arena.Node;
import behavior.Behavior;
import graphics.Point;
import gui.WaitingFrame;
import javax.swing.JFrame;

/**
 * Subclass of the robot class, maintains properties and
 * methods used by physical robots to move along the grid.
 *
 * @author Gabriel Loewen
 * @author Jimmy Weston
 * @author Jackie O'Quinn
 */
public class PhysicalRobot extends Robot {

    protected int leftPath,  fwdPath,  rightPath;

    /**
     * Constructor, accepts a robot name, location, and heading
     * @param name      Name of the robot
     * @param point     Position of the robot in the grid in (X,Y) format
     * @param head      The initial heading of the robot (N, E, S, or W)
     * @param left      1 indicates left path exists.  0 indicates the left path does not exist
     * @param fwd       1 indicates forward path exists.  0 indicates the forward path does not exist
     * @param right     1 indicates right path exists.  0 indicates the right path does not exist
     */
    public PhysicalRobot(String name, Point point, char head, int left, int fwd, int right) {
        super(name, point, head, false);
        this.leftPath = left;
        this.fwdPath = fwd;
        this.rightPath = right;
    }

    /**
     * The process to move a robot requires three threads which are
     * defined by the following:
     * WaitThread - Instantiates a jframe which acts as a progress monitor
     * while the behavior generates a path.
     *
     * PathGenerator - Is called by the wait thread to generate a path from
     * the current behavior
     *
     * MoveThread - Gets the generated path from the behavior and operates
     * the movement of the robot according to the path, or for non-path-driven
     * behaviors a heuristical method is usually used to generate the next
     * move.
     *
     * Uses a thread to generate a path from the behavior that the robot has
     * been assigned and then moves the robot.  The reason why so many threads
     * are being used is because without these threads the dialog box containing
     * the status of the behavior would be blocked from executing.
     *
     * @param goal      The goal location that the robot is instructed to move to.
     */
    @Override
    public void move(String goal) {
        MoveThread m = new MoveThread();
        PathGenerator p = new PathGenerator(goal, m);
        WaitThread w = new WaitThread(p);
        new Thread(w).start();
        behavior.getPath(goal, false);
        //printPositionMatrix(getPositionMatrix());

    }

    /**
     * Receieve data from the database regarding this robot.  This data includes
     * current position, heading and available paths.
     */
    public void receive() {
        int[] data;
        while ((data = DBComm.read(this.getID())) == null) {
            try {
                Thread.sleep(3000);
            } catch (Exception ex) {
            }
        }
        update(data);
        getArena().getGrid().redrawRobot(oldLocation, location);
    }

    /**
     * Sends the next move to the database.  This data is then read by the
     * host computer and sent over bluetooth to the cooresponding robot.
     */
    public void send() {
        DBComm.write(getID(), (char) behavior.nextMove()[0]);
    }

    /**
     * Update the nodes with the current data
     *
     * @param data  Data receieved from the database
     */
    @Override
    public void update(int[] data) {
        super.update(data);
        updateNodeMap(data[3], data[4], data[5]);
    }

    /**
     * Updates the hashmap of nodes with the data regarding available paths
     *
     * @param leftLine  1 indicates left path exists.  0 indicates the left path does not exist
     * @param fwdLine   1 indicates forward path exists.  0 indicates the forward path does not exist
     * @param rightLine 1 indicates right path exists.  0 indicates the right path does not exist
     */
    private void updateNodeMap(int leftLine, int fwdLine, int rightLine) {
        Point[] pList = null;
        if (Character.toUpperCase(heading) == 'N') {
            pList = new Point[]{new Point(location.x - 1, location.y),
            new Point(location.x, location.y + 1),
            new Point(location.x + 1, location.y)};
        } else if (Character.toUpperCase(heading) == 'S') {
            pList = new Point[]{new Point(location.x + 1, location.y),
            new Point(location.x, location.y - 1),
            new Point(location.x - 1, location.y)};
        } else if (Character.toUpperCase(heading) == 'W') {
            pList = new Point[]{new Point(location.x, location.y - 1),
            new Point(location.x - 1, location.y),
            new Point(location.x, location.y + 1)};
        } else if (Character.toUpperCase(heading) == 'E') {
            pList = new Point[]{new Point(location.x, location.y + 1),
            new Point(location.x + 1, location.y),
            new Point(location.x, location.y - 1)};
        }
        if (leftLine == 0 && getArena().nodeMap.containsKey(pList[0].toString())) {
            getArena().nodeMap.get(pList[0].toString()).setColor(Node.DELETED);
        }
        if (fwdLine == 0 && getArena().nodeMap.containsKey(pList[1].toString())) {
            getArena().nodeMap.get(pList[1].toString()).setColor(Node.DELETED);
        }
        if (rightLine == 0 && getArena().nodeMap.containsKey(pList[2].toString())) {
            getArena().nodeMap.get(pList[2].toString()).setColor(Node.DELETED);
        }
    }

    /**
     * Thread which generates a path depending using the behavior defined by
     * this robot.
     *
     * @author Gabriel Loewen
     */
    class PathGenerator implements Runnable {

        private String goal;
        MoveThread move;
        private boolean end = false;

        /**
         * Constructor, accepts a goal location as well as the runnable object
         * used to move the robot.
         * @param g     The goal location in "X,Y" format
         * @param m     Runnable object created to move the robot
         */
        public PathGenerator(String g, MoveThread m) {
            move = m;
            goal = g;
        }

        /**
         * Cancels the path generation.  Called by depressing the cancel
         * button in the path generation dialog box.
         */
        public void cancel() {
            end = true;
        }

        /**
         * Calls the getPath method from the behavior.
         *
         * @see Behavior
         */
        public void run() {
            behavior.getPath(goal, end);
            try {
                Thread.sleep(1);
            } catch (Exception ex) {
            }
            if (!end) {
                new Thread(move).start();
            }
        }
    }

    /**
     * Moves the robot according to the generated path
     */
    class MoveThread implements Runnable {

        /**
         * Default Constructor
         */
        public MoveThread() {
        }


        /**
         * This method will sequentially send and receive data while the behavior
         * has more moves pending.
         */
        public void run() {
            while (behavior.hasMove()) {
                send();
                receive();
                

            }
        }
    }

    /**
     * Thread which waits for the path to be generated using a gui
     */
    class WaitThread implements Runnable {

        private WaitingFrame waiting;
        private PathGenerator path;
        private boolean prematureEnd;

        /**
         * Constructor which accepts a Runnable PathGenerator object.  Displays
         * a JFrame dialog with a progress bar and a cancel button.
         * @param p     Path generator thread.
         */
        public WaitThread(PathGenerator p) {
            waiting = new WaitingFrame();
            behavior.resetProgress();
            waiting.setBehavior(behavior);
            waiting.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
            waiting.setLocationRelativeTo(null);
            waiting.setVisible(true);
            prematureEnd = false;
            path = p;
        }

        /**
         * Retrieve the current behavior progress and update the dialog.
         * If the user clicks the cancel button the cancel method will be
         * invoked and the behavior will be cancelled.
         */
        public void run() {
            new Thread(path).start();
            while (behavior.getProgress() < 100 && !prematureEnd) {
                waiting.progress.setValue((int) behavior.getProgress());
                if (waiting.exit) {
                    path.cancel();
                    prematureEnd = true;
                }
                try {
                    Thread.sleep(1);
                } catch (Exception ex) {
                }
            }
            waiting.setVisible(false);
            waiting.dispose();
        }
    }
}
